import { DictArray } from "../utils/array.util"
import clientEnum from "./client.enum"
import CollectionSubcribeMessage from "./CollectionSubcribeMessage"
import CollectionUnsubscribeMessage from "./CollectionUnsubscribeMessage"
import CollectionUpdateFacade from "./CollectionUpdateFacade"
import CollectionUpdateObserverAdaptor from "./CollectionUpdateObserverAdaptor"

export default class CollectionRegistration {
  messenger: any
  collectionCache: any
  observers: any
  elementFactories: any
  collectionToElementFactoryMap: any
  collectionUpdateObserver: CollectionUpdateObserverAdaptor
  constructor(t) {
    this.messenger = t
    this.collectionCache = {}
    this.observers = new DictArray()
    this.elementFactories = {}
    this.collectionToElementFactoryMap = {}
    this.collectionUpdateObserver = new CollectionUpdateObserverAdaptor(new CollectionUpdateFacade(this))
    this.messenger.addObserver(this.collectionUpdateObserver)
  }
  dispose() {
    for (const t of this.observers.keys) this.messenger.send(new CollectionUnsubscribeMessage(t))
    this.messenger.removeObserver(this.collectionUpdateObserver)
  }
  register(t, e) {
    const s = this
    if (e) {
      const s = this.elementFactories[e]
      if (!s) throw new Error(`${e} element factory was not provided for ${t}`)
      this.collectionToElementFactoryMap[t] = s
    }
    return new (class Collection {
      subscribe(e) {
        return s.subscribe(t, e)
      }
    })()
  }
  initElementFactories(t, ...e) {
    for (const s of e) {
      const e = new s(t)
      this.elementFactories[e.collectionKey] = e
    }
  }
  observe(t) {
    switch (t.operation) {
      case clientEnum.e.ITEM_ADDED: {
        const { collectionKey: e, collectionIndex: s, item: i } = t
        this.onItemAdded(e, s, i)
        break
      }
      case clientEnum.e.ITEM_REMOVED: {
        const { collectionKey: e, collectionIndex: s } = t
        this.onItemRemoved(e, s)
        break
      }
      case clientEnum.e.ITEM_UPDATED: {
        const { collectionKey: e, collectionIndex: s, item: i } = t
        this.onItemUpdated(e, s, i)
        break
      }
      case clientEnum.e.COLL_UPDATED: {
        const { collectionKey: e } = t
        this.onCollectionUpdated(e)
        break
      }
    }
  }
  onItemAdded(t, e, s) {
    const i = this.collectionCache[t] || {}
    this.collectionCache[t] = i
    const r = this.collectionToElementFactoryMap[t]
    i[e] = r ? r.create(s) : s
    const n = this.observers.getValuesAtKey(t)
    for (const t of n) t.onAdded && t.onAdded(e, i[e], i)
  }
  onItemRemoved(t, e) {
    const s = this.collectionCache[t]
    if (!s) return
    const i = s[e]
    if ((delete s[e], i)) {
      const r = this.observers.getValuesAtKey(t)
      for (const t of r) t.onRemoved && t.onRemoved(e, i, s)
    }
  }
  onItemUpdated(t, e, s) {
    const i = this.collectionCache[t]
    if (!i) return
    const r = this.collectionToElementFactoryMap[t]
    r ? r.create(s, i[e]) : (i[e] = s)
    const n = this.observers.getValuesAtKey(t)
    for (const t of n) t.onUpdated && t.onUpdated(e, s, i)
  }
  onCollectionUpdated(t) {
    const e = this.collectionCache[t]
    if (!e) return
    const s = this.observers.getValuesAtKey(t)
    for (const t of s) t.onCollectionUpdated && t.onCollectionUpdated(e)
  }
  subscribe(t, e) {
    0 === this.observers.valuesPerKey(t) && this.messenger.send(new CollectionSubcribeMessage(t)), this.observers.add(t, e)
    const s = this.collectionCache[t]
    if (s) {
      for (const t of Object.keys(s)) e.onAdded && e.onAdded(t, s[t], s)
      e.onCollectionUpdated && e.onCollectionUpdated(s)
    }
    return { cancel: () => this.unsubscribe(t, e) }
  }
  unsubscribe(t, e) {
    this.observers.remove(t, e),
      0 === this.observers.valuesPerKey(t) && (this.messenger.send(new CollectionUnsubscribeMessage(t)), delete this.collectionCache[t])
  }
}
